#!/usr/bin/python

class IllegalMove(Exception):
    pass

size = 3
dim = 4 # try to keep it as flexible as possible!
colours = {0: ' ', 1: 'X', -1: 'O'}

board_size = size ** dim
diag = [range(size), range(size-1,-1,-1)]
strt = [[k,k,k] for k in range(size)]
psbl = diag + strt # possibilities

def convert(coord):
    """ convert coordinates to single number
    """
    num = 0
    for k, component in enumerate(coord):
        if component in range(size):
            num += component * size ** (dim - (k + 1))
        else:
            raise IllegalMove('Coordinate outside board.')
    return num

# this function should be moved to text version
def print_board(pos):
    """ represent the board on screen
    via ASCII art
    """
    ascii_array = map (lambda c: colours[c], pos.board_array)
    for k in range(size**2):
        start = size**2 * k
        print ascii_array[start:start+size**2]

# list of all coordinates
coord_list = [[k, l, m, n]
        for k in range(size)
        for l in range(size)
        for m in range(size)
        for n in range(size)]

# find all possible rows (assumes dim == 4):
# at first go through every possibility, then remove dublicates 
# think of more elegant ways to define the set of rows!
rows = [[[k[x], l[x], m[x], n[x]] for x in range(size)]
        for k in psbl
        for l in psbl
        for m in psbl
        for n in psbl
        if k in diag or l in diag or m in diag or n in diag]
for row in rows:
    row.sort()
for row in rows[:]:
    if rows.count(row) > 1:
        rows.remove(row)
raw_rows = [map(convert, row) for row in rows]

class Position:
    def __init__(self):
        self.whose_turn = 1
        self.board_array = board_size * [0] # initialize empty board

    def check(self, coord):
        num = convert(coord)
        return self.board_array[num]

    def set(self, num, col):
        if num < board_size and num > -1:
            self.board_array[num] = col
        else:
            raise IllegalMove('Coordinate outside board.')

    def move(self, num):
        """ put an X or O at coordinate
        and change turns 
        """
        if not self.board_array[num] == 0:
            raise IllegalMove('Field is already occupied.')
        self.set(num, self.whose_turn)
        self.whose_turn *= -1

    def are_squares_empty(self):
        """ Game is over when False
        """
        return 0 in self.board_array

    def find_empty_squares(self):
        """ Return the numbers of empty squares
        """
        return filter(lambda k: self.board_array[k] == 0, range(board_size))
    
    def count_rows(self, col):
        """ Count rows of Colour col
        """
        counter = 0
        for row in raw_rows:
            row_result = [self.board_array[num] for num in row]
            if row_result.count(col) == size:
                counter += 1
        return counter
    
    def count_patterns(self):
        """ Count open rows with number of X or O missing
        needed to rate position
        """
        open_for_X = [0] * size
        open_for_O = [0] * size
        for row in raw_rows:
            row_result = [self.board_array[num] for num in row]
            if 1 in row_result and -1 not in row_result:
                open_for_X[row_result.count(0)] += 1
            elif -1 in row_result and 1 not in row_result: 
                open_for_O[row_result.count(0)] += 1
        return [open_for_X, open_for_O]
        # Could be done more efficiently: count changes after every move!
